
/*-----------------------------------------------------------------------
 * Copyright (C) 2001 Green Light District Team, Utrecht University 
 *
 * This program (Green Light District) is free software.
 * You may redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by
 * the Free Software Foundation (version 2 or later).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * See the documentation of Green Light District for further information.
 *------------------------------------------------------------------------*/

package gld.sim;

import gld.Model;
import gld.GLDException;

import gld.algo.dp.*;
import gld.algo.edit.ShortestPathCalculator;
import gld.algo.tlc.*;
import gld.edit.Validation;
import gld.infra.*;
import gld.sim.stats.StatisticsController;
import gld.sim.stats.TrackerFactory;
import gld.utils.Arrayutils;
import gld.utils.NumberDispenser;
import gld.xml.*;

import java.awt.Point;
import java.awt.Color;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import java.util.*;


import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


/**
 *
 * The heart of the simulation.
 *
 * @author Group Model
 * @version 1.0
 */

public class SimModel extends Model implements XMLSerializable
{
	
	private static final String statsCyclesKey = "gld.sim.stats.sender.cycles";
	/** The pseudo-random-number-generator we need in this simulation */
	protected Random generator;	
	/** The second thread that runs the actual simulation */
	protected SimModelThread thread;
	/** The current cycle we're in */
	protected int curCycle;
	/** The Driving Policy in this Simulation */
	protected static DrivingPolicy dp;
	/** The TrafficLightControlling Algorithm */
	protected TLController tlc;
	/** The Thing that makes all Trafficlights shudder */
	protected SignController sgnctrl;
	/** Name of the simulation */
	protected String simName="untitled";
	/** A boolean to keep track if this sim has already run (ivm initialization) */
	protected boolean hasRun = false;
	/** Indicates if roadusers cross nodes or jump over them. */
	public static boolean CrossNodes = true;
	
	/** Indicates wheter the StatisticsSender thread must run or not */
	boolean active = false;
	protected StatisticsSender statsSenderThread = null;
	protected LinkedList spawnFreqsList = null;
	protected Iterator iFreqs = null;
	protected int nroFranja = 0;
	
	protected String file_franjas_key = "gld.sim.timeInterval.source";
	protected ResourceBundle rb = null;
	
	/**
	 * Creates second thread
	 */	
	public SimModel() {
		// TODO place to read
		thread = new SimModelThread();
		thread.start();
	//	threadUpdate = new SimModelFreqsUpdate();
		//System.out.println("va a arrancar el thread de configuracion de freqs");
	//	threadUpdate.start();
		curCycle = 0;
		generator = new Random();
		sgnctrl = new SignController(tlc, infra);
		
		
		//GASTON: getting resource bundle
		rb = ResourceBundle.getBundle("simulation");
		
		//GASTON: creting thread to send statistics
		statsSenderThread = new StatisticsSender();
		statsSenderThread.setModel(this);
		statsSenderThread.start();

	}
	
	public void setInfrastructure(Infrastructure i) {
		pause();
		
		super.setInfrastructure(i);
		if(tlc!=null)
			tlc.setInfrastructure(i);
		if(sgnctrl!=null)
			sgnctrl.setInfrastructure(i);
	}

	/** Returns the current cycle */
	public int getCurCycle() { return curCycle; }
	/** Sets the current cycle */
	public void setCurCycle(int c) { curCycle = c; tlc.setCurCycle(c); }

	/** Returns the current Driving Policy */
	public static DrivingPolicy getDrivingPolicy() { return dp; }
	/** Sets the current DrivTLController */
	public void setDrivingPolicy(DrivingPolicy _dp) { dp = _dp; }

	/** Returns the current TLController */
	public TLController getTLController() { return tlc; }
	/** Sets the current TLController */
	public void setTLController(TLController _tlc) {
		tlc = _tlc;
		sgnctrl.setTLC(tlc);
	}
	
	/** Returns the random number generator */
	public Random getRandom() { return generator; }
	/** Sets the random number generator */
	public void setRandom(Random r) { generator = r; }

	/** Returns the name of the simulation */
	public String getSimName() { return simName; }
	/** Sets the name of the simulation */
	public void setSimName(String s) { simName = s; }

	/** Returns the pseudo-random-number generator of this Model */
	public Random getRNGen() {return generator;}

	/** Sets spawn frequency for given node and ru type. */
	public void setSpawnFrequency(EdgeNode en, int rutype, float newspawn) {
		en.setSpawnFrequency(rutype, newspawn);
		setChanged();
		notifyObservers();
	}

	/**
	 * Stops the simulation.
	 * This should only be called when the program exits.
	 * To start a new simulation, the simulation should be paused
	 * with a call to pause(), then followed by a call to reset(),
	 * and finally resumed with unpause().
	*/
	public void stop() {
		thread.die();
	}
	
	/**
	 * Pauses the simulation
	 */
	public void pause() {
		thread.pause();
	}
	
	/**
	 * Unpauses the simulation
	 */
	public void unpause() {
		thread.unpause();
	}
	
	public boolean isRunning() {
		return thread.isRunning();
	}
	
	/**
	 * Resets data
	 */
	public void reset() throws SimulationRunningException {
		if (thread.isRunning()) throw new SimulationRunningException("Cannot reset data while simulation is running.");
		infra.reset();
		tlc.reset();
		curCycle = 0;
		generator = new Random();
		TrackerFactory.resetTrackers();

		setChanged();
		notifyObservers();
	}


	public void disableTraffic(Drivelane lane){
		
		
		try {
			
			Road actual_road = lane.getRoad();
			actual_road.setEnabled(false);
			Node[] nodes = infra.getAllNodes();
			for (int i=0;i<nodes.length;i++){
				nodes[i].remAllPaths();
				if (nodes[i] instanceof Junction){
					((Junction)nodes[i]).updateAllAvailableRoads();
				}
			}
			ShortestPathCalculator spcalculator = new ShortestPathCalculator();
			spcalculator.calcAllShortestPaths(infra);
			/*Validation validation = new Validation(infra);
			validation.validate();*/
			
		}catch(Exception e){
			e.printStackTrace();
		}
			
		
	}

	public void enableTraffic(Drivelane lane){
		try {
			
			Road actual_road = lane.getRoad();
			actual_road.setEnabled(true);
			Node[] nodes = infra.getAllNodes();
			for (int i=0;i<nodes.length;i++){
				nodes[i].remAllPaths();
				if (nodes[i] instanceof Junction){
					((Junction)nodes[i]).updateAllAvailableRoads();
				}
			}
			ShortestPathCalculator spcalculator = new ShortestPathCalculator();
			spcalculator.calcAllShortestPaths(infra);
		}catch(Exception e){
			e.printStackTrace();
		}
	}






	/**
	 * Does 1 step in the simulation. All cars move, pedestrians get squashed etc...
	 */
	public void doStep( ) {
		
		curCycle++;
		tlc.setCurCycle(curCycle);
		
		int statisticsInterval = Integer.parseInt(rb.getString(statsCyclesKey));
		if (curCycle% statisticsInterval == 0){
			//GASTON:we try to export runtime information
			sendStatistics();			
		}
		//System.out.println("CurTLC:"+tlc.getXMLName());
		if (! hasRun)
		{	initialize();
			hasRun=true;
		}
		try {
			Enumeration	specialNodes=Arrayutils.getEnumeration(infra.getSpecialNodes());
			while (specialNodes.hasMoreElements())
				((SpecialNode)(specialNodes.nextElement())).doStep(this);
			moveAllRoadusers();
			spawnNewRoadusers();
			//sgnctrl.switchSigns();
			sgnctrl.switchSignsMultiplexer();
		}
		catch (Exception e) {
			System.out.println("The simulator made a booboo:");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
		
		setChanged();
		notifyObservers();
		/////////////////////////////////////////////////////////////////////////
		if (curCycle == 4000) {
			pause();
			Toolkit.getDefaultToolkit().beep();
		}
		/////////////////////////////////////////////////////////////////////////
	}
	
	public void initialize ()
	{	SAVE_STATS = true;
		Enumeration e=Arrayutils.getEnumeration(infra.getSpecialNodes());
		while (e.hasMoreElements())
			((SpecialNode)(e.nextElement())).start();
			
			
		//GASTON: we load the different spwan frequencies
		spawnFreqsList = new LinkedList();
		InputSource source = new InputSource(rb.getString(file_franjas_key));
		DOMParser parser = new DOMParser();
		try {
			parser.parse(source);
		} catch (SAXException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		Document doc = parser.getDocument();
		NodeList nodeList = doc.getElementsByTagName("franja");
		for (int i=0;i<nodeList.getLength();i++){
			System.out.println(nodeList.item(i).getNodeName());
			NodeList edgeNodesList = nodeList.item(i).getChildNodes();
			HashMap spawnFreqsHM = new HashMap();
			for (int j=0;j<edgeNodesList.getLength() ;j++){
				if (!edgeNodesList.item(j).getNodeName().equals("edgenode")) continue;
				String nodeid = null;
				NamedNodeMap nnm = edgeNodesList.item(j).getAttributes();
				if(nnm != null )
				{
					int len = nnm.getLength() ;
					Attr attr;
					for ( int a = 0; a< len; a++ )
					{
						attr = (Attr)nnm.item(a);
						System.out.print(' ' + attr.getNodeName() + "=\"" + attr.getNodeValue() +  '"' );
						if (attr.getNodeName().equals("id")) nodeid = attr.getNodeValue();
					}
					if (nodeid != null){
						NodeList spawnFreqsList = edgeNodesList.item(j).getChildNodes();
						String[] freqs = {"0.0","0.0","0.0"};
				
						for (int k=0;k < spawnFreqsList.getLength();k++)
						{
							org.w3c.dom.Node nodo= spawnFreqsList.item(k);
							int type = nodo.getNodeType();
							//if (type == org.w3c.dom.Node.)
							String nodeName = nodo.getNodeName();
							//String nodeValue = nodo.getNodeValue();
							//String nodeName = spawnFreqsList.item(k).getNodeName();
							//String nodeValue = spawnFreqsList.item(k).getFirstChild().getNodeValue();
							if (nodeName.equals("car")){
								org.w3c.dom.Node child = nodo.getFirstChild();
								freqs[0] = child.getNodeValue();
							}
							if (nodeName.equals("bus")){
								org.w3c.dom.Node child = nodo.getFirstChild();
								freqs[1] = child.getNodeValue();
							}
							if (nodeName.equals("bicycle")){
								org.w3c.dom.Node child = nodo.getFirstChild();
								freqs[2] = child.getNodeValue();
							}
						} 
						spawnFreqsHM.put(nodeid,freqs);	
					}
			
				}
		
			}
			spawnFreqsList.add(spawnFreqsHM);
	
				
	
		} 
  		
		iFreqs = spawnFreqsList.iterator();
		//updateSpawnFreqs();
		
	}
	
	/** Gets the speed of the simulation */
	public int getSpeed() { return thread.getSleepTime(); }
	/** Sets the speed of the simulation */
	public void setSpeed(int s) { thread.setSleepTime(s); }


	/** New road users are placed on the roads when necessary. When roads are full,
	 *  new road users are queued.
	 */
	public void spawnNewRoadusers() throws InfraException,ClassNotFoundException
	{
		SpecialNode[] specialNodes = infra.getSpecialNodes();
		EdgeNode edge;
		Roaduser r;
		int num_edges=specialNodes.length;
		LinkedList wqueue;
		ListIterator list;
		
		for(int i=0;i<num_edges;i++) {
			if ( ! (specialNodes[i] instanceof EdgeNode) )
				break;
			else
				edge=(EdgeNode)(specialNodes[i]);
			boolean placed = false;
			wqueue = edge.getWaitingQueue();
			list = wqueue.listIterator();
			while(list.hasNext()) {
				r = (Roaduser) list.next();
				if(placeRoaduser(r, edge)) {
					list.remove();
				}
			}
			
			SpawnFrequency[] freqs = edge.getSpawnFrequencies();
			DestFrequency[][] destfreqs = edge.getDestFrequencies();
			int num_freqs = freqs.length;
			int cur_index;
			int[] freqIndexes = new int[num_freqs];
			
			for (int nrs=0;nrs<num_freqs;nrs++)
				freqIndexes[nrs] = nrs;			//Shuffle the indexes
				
			Arrayutils.randomizeIntArray(freqIndexes, generator);
			
			for(int j=0;j<num_freqs;j++) {
				//First try to place new road users on the road.
				cur_index = freqIndexes[j];
				if(freqs[cur_index].freq >= generator.nextFloat()) {
					int ruType = freqs[cur_index].ruType;
					/* Spawn road user of type freqs[i].ruType to a random destination.
					* When all drivelanes are full the road users are queued.
					*/
					SpecialNode dest = getRandomDestination( specialNodes, edge, ruType, destfreqs );
					r = RoaduserFactory.genRoaduser(ruType, edge, dest, 0);
					r.setDrivelaneStartTime(curCycle);
					//System.out.println("Origen: " + edge.getId()+ " - Destino: "+dest.getId());
					
					// There might be space for r
					if(!placeRoaduser(r, edge)) {
						// There was no place on any possible lane.
						list.add(r);
					}
				}//end if
			}//end for
		}
	}
	
	/** A road user is placed on the given edge node. When road is full the ru is queued */
	private boolean placeRoaduser(Roaduser r, SpecialNode edge)
	{
		Drivelane found = findDrivelaneForRU(r,edge);
		if(found==null)
			return false;
		else {
			// There is room for me!
			try {
				//System.out.println("Adding RU with type:"+r.getType()+" to lane:"+found.getSign().getId()+" going to Node:"+found.getNodeLeadsTo().getId()+" at pos:"+found.getNodeLeadsTo().isConnectedAt(found.getRoad())+" with type:"+found.getType());
				//found.addRoaduserAtEnd(r, found.getLength()-r.getLength());
				found.addRoaduserAtEnd(r);
				r.addDelay(curCycle - r.getDrivelaneStartTime());
				r.setDrivelaneStartTime(curCycle);
				return true;
			}
			catch(Exception e)
			{ return false; }
		}
	}
	
	private Drivelane findDrivelaneForRU(Roaduser r, SpecialNode e)
	{
		SpecialNode dest = (SpecialNode) r.getDestNode();
		Drivelane[] lanes = (Drivelane[]) e.getShortestPaths(dest.getId(), r.getType()).clone();
		Arrayutils.randomizeArray(lanes);
		int num_lanes = lanes.length;
		for(int i=0;i<num_lanes;i++) {
			if(lanes[i].isLastPosFree(r.getLength()))
				return lanes[i];
		}
		//System.out.println("Couldnt place RU");
		return null;
	}	
	
	/** Get a completely random destination, don't choose moi*/
	public SpecialNode getRandomDestination(SpecialNode moi) throws InfraException
	{	SpecialNode[] dests=infra.getSpecialNodes();
		if (dests.length < 2 )
			throw new InfraException
			("Cannot choose random destination. Not enough special nodes.");
		SpecialNode result;
		while (moi==(result=dests[(int)(generator.nextFloat()*dests.length)]));
		return result;	
	}
	
	/*Choose a destination*/
	private SpecialNode getRandomDestination( SpecialNode[] dests, SpecialNode here, int ruType, DestFrequency[][] destfreqs )
	{
		//GASTON: ESTA FUNCION DECIDE EL DESTINO. HABR�A QUE CAMBIAR destfreqs para influir en la decisi�n
		//destIds: va a tener los ids de los posibles nodos destino
		/*destfreqs: es una matriz con las frecuencias(una fila por cada nodo de tipo EdgeNode, 1 columna por cada ruType (RoadUserType(ej: auto)) ). 
		Cada nodo tiene una matriz destfreqs indicando las frecuencias hacia cada nodo destino posible*/
		int[] destIds = here.getShortestPathDestinations(ruType);
		float choice = generator.nextFloat() ; //genero un nro al azar entre 0 y 1
		float total = 0f ;
		/*All frequencies are between 0 and 1, but their total can be greater than 1*/
		for ( int i=0; i<destIds.length ; i++ )	{	
			//sumo todas las frecuencias (entre todos los nodos destino posible) para el ruType correspondiente
			for ( int j=0; j<destfreqs[i].length ; j++ ) {
				if ( destfreqs[destIds[i]][j].ruType == ruType ) {
					total += destfreqs[destIds[i]][j].freq ;
				}
			}
		}
		
		float sumSoFar = 0f ;
		int j = 0 ;
		int index = 0 ;
		boolean foundIndex = false ;
		while ( j<destIds.length && !foundIndex ) {
			//ac?se termina de decidir a qu?nodo destino se ir?
			for ( int i=0; i<destfreqs[j].length ; i++ ) {
				if ( destfreqs[destIds[j]][i].ruType == ruType ) {
					float now =	(destfreqs[destIds[j]][i].freq)/total ;
					if (now+sumSoFar >= choice ) {
						foundIndex = true ;
						index = j;
					}
					else {
						sumSoFar += now ;
					}
				}
			}
			j++;
		}
		
		return dests[destIds[index]] ;
	}
	
	/*Get a random index out of the lanes*/
	private int getRandomLaneNr(Drivelane[] lanes) {
		int ind = (int) Math.floor(generator.nextFloat()*(lanes.length));
		while(ind!=lanes.length)
			ind = (int) Math.floor(generator.nextFloat()*(lanes.length));
		return ind ;
	}
	
	/**
	 *
	 * moving all Roadusers to their new places
	 *
	 * @author Blah... why put an author tag at every 10-line piece of code? -> Just because we can! MUHAHAahahahahah!
	 */
	public void moveAllRoadusers() throws InfraException
	{	// Line below is faster than the obvious alternative
		Enumeration	lanes=Arrayutils.getEnumeration(infra.getAllInboundLanes().toArray());
		Drivelane lane;
		while (lanes.hasMoreElements())	{
			lane=(Drivelane)(lanes.nextElement());
			// First you should check wheter they are already moved ......
			// ??? Why need to check it?? - park
			if(lane.getCycleMoved() != curCycle)
				moveLane(lane, null);
		}
	}


	/**
	 * moving all roadusers from one lane to their new places
	 *
	 * @author Jilles V, Arne K, Chaim Z and Siets el S
	 * @param lane The lane whose roadusers should be moved
	 * @param callingLanes Vector of drivelanes, for recursive internal use only, this parameter should have the value null, when called from the outside
     * @version 1.0
	 */
	protected void moveLane(Drivelane lane, Vector callingLanes) throws InfraException
	{
		// (SBC) check the roadusers if they have to change speed on this lane
		checkRoaduserSpeed(lane);
		
		
		LinkedList queue;
		ListIterator li;
		Drivelane sourceLane, destLane;
		Node node;
		Sign sign;
		Roaduser ru;

		int ru_pos, ru_des, ru_speed, ru_type, ru_len;

		//GASTON: we calculate the statistics for the lane, we will do it in every cycle.
		lane.processStats();
		//
		
		sign = lane.getSign();
		queue = lane.getQueue();
		li = queue.listIterator();
		
		while(li.hasNext())	{
			try
			{
				ru = (Roaduser) li.next();
			}
			catch(Exception e)
			{
				// When this exception is thrown you removed the first element of the queue, therefore re-create the iterator.
				System.out.println("CME");
				li = queue.listIterator();
				continue;				
			}
			
			if(!ru.didMove(curCycle)) {	// Only attempt to move this RU when it hasnt already
				ru.setCycleAsked(curCycle);
				ru_pos   = ru.getPosition();
				ru_speed = ru.getSpeed();
				ru_len   = ru.getLength();
				
				//Calculating the ranges per drivelane in which this roaduser could get into	
				node = sign.getNode();
				ru_type = ru.getType();
				ru_des  = ru.getDestNode().getId();
				
				Drivelane[] possiblelanes = node.getShortestPaths(ru_des, ru_type);
				int lanes = possiblelanes.length;

				Point[] ranges = new Point[lanes];
				
				// Is our own lane free? If there is any ?????
				if(lanes>=1)
				{
					int i=lane.getPosFree(li,ru_pos,ru_len,ru_speed,ru);
					ranges[0] = new Point(i, ru_pos);	//Range on own lane is [i, ru_pos]
	
					lanes--;
					//Is this lane clear, then figure out the other lanes
					if(i==0 && ru_pos-ru_speed <= 0)
					{
						for(; lanes>0; lanes--)	{
							Drivelane test_lane = possiblelanes[lanes];
							int range = test_lane.getLength()-1;
							int max = range - (ru_speed-ru_pos);
							while (test_lane.isLastPosFree(ru_len) && range > max )	{
								range--;						
							}					
							ranges[lanes] = new Point(range, test_lane.getLength()-1);	//The range is [range,test_lane]
						}
					}
					else	//Don't even bother, empty the ranges of the other lanes
					{
						for(; lanes>0; lanes--)	{
							ranges[lanes] = new Point(0, 0);	//The range is [range,test_lane]
						}
					}
				}
				// =======================================
				
				// The R stuff is now calculated
			
				
				// Handle Roadusers that possibly can cross a Node
				if(ru_pos-ru_speed < 0) {
					// Handle Roadusers that get to Special Nodes
					// System.out.println("Possibly can cross...");										
					if(node instanceof SpecialNode) {
						// System.out.println("At a SpecialNode, will be removed");
				    	if(ru_pos==0 || 0==lane.getPosFree(li, 0, ru_len, ru_speed, ru)) {
				    		ru.setPosition(-1);
				    		ru.setPrevSign(-1);
							li.remove();
							tlc.updateRoaduserMove(ru, lane, sign, ru_pos, null, null, 0, possiblelanes, ranges, null);
							// Give RoadUser to Edgenode to unload stat information
							node.processStats(ru, curCycle, sign);
							((SpecialNode)(node)).enter(ru);
							ru = null;
						}
					}
					// Handle Roadusers that are (or nearly) at a Sign
					else if(lane.getSign().getType()==Sign.NO_SIGN || lane.getSign().mayDrive()) {
						// System.out.println("nearly at Sign");
						// Can cross-check
						if(ru_pos==0 || 0==lane.getPosFree(li, 0, ru_len, ru_speed, ru)) {
							ru_type = ru.getType();
							ru_des  = ru.getDestNode().getId();


							
							//Drivelane[] lanesleadingfrom = node.getLanesLeadingFrom(lane, ru_type);
							Drivelane[] shortestpaths = node.getShortestPaths(ru_des, ru_type);
							
							Drivelane[] lanesleadingfrom = node.getAvailableLanesLeadingFrom(lane, ru_type);
							destLane = dp.getDirection(ru, lane, lanesleadingfrom, shortestpaths);
							
							
							
							//GASTON: I added this lines to check wheter we could find a path or not
							if (destLane==null){
								//if I we could not find an alternative path for the road users, we erase them from the lane.
								lane.getQueue().clear();
								return;									
							}
							//FIN DEL CHEQUEO
							
							// Check if there is room on the node
							if(destLane.isLastPosFree(ru_len)) {
								// Let the RU touch the Sign to refresh/unload some statistical data
								// Remove the RU from the present lane, and place it on the destination lane
								try{
									node.processStats(ru, curCycle, sign);
									destLane.addRoaduserAtEnd(ru);
									ru.setPrevSign(lane.getSign().getId());
									li.remove();
									tlc.updateRoaduserMove(ru, lane, sign, ru_pos, destLane, destLane.getSign(), ru.getPosition(), possiblelanes, ranges, destLane);
								}
								catch(Exception e) { System.out.println("Something screwd up in SimModel.moveLane where a Roaduser is about to cross:"+e); }
							}						
							// Otherwise, check if the next lane should move, and then do just that
							else {
								// If the position is not free, then check if it already moved this turn, if not:
								if (curCycle != destLane.getCycleAsked()) 
								{
									//System.out.println("DestLane hasnt been asked whether it has moved..");
									if (curCycle != destLane.getCycleMoved()) 
									{
										//System.out.println("Waiting for another lane to move..");
										Vector ln = callingLanes;										
										
										if (ln==null)
										{
											ln = new Vector();
										}
										// Detect when there is a cycle of waiting lanes		
										boolean cycle = false;
										if (ln.size()>0) if (ln.firstElement()==lane) cycle = true;
										
										if (cycle)
										{
											// If that's the case, they should all do one step
											
											Vector rus = new Vector();		
										
											// first remove all first cars of all drivelanes and store them in a vector
											for (Enumeration e=ln.elements();e.hasMoreElements();)
											{
												Drivelane dl = (Drivelane) e.nextElement();
												Roaduser r   = dl.getFirstRoaduser();
												rus.addElement(r);
												dl.remRoaduserAtStart();
								r.setPrevSign(dl.getSign().getId());
								node.processStats(r, curCycle, dl.getSign());
											}
											
											// Rotate all roadusers one position to the right
											Roaduser r = (Roaduser) rus.elementAt(rus.size()-1);
											rus.remove(r);
											rus.insertElementAt(r,0);
											
											// Now, all waiting drivelanes can move
											for (Enumeration e=ln.elements();e.hasMoreElements();)
											{
												moveLane((Drivelane) e.nextElement(), null);
											}
											
											// Now place all stored Roadusers back at the end of another drivelane
											for (int i=0; i<rus.size(); i++)
											{
												Drivelane dlFrom = (Drivelane) ln.elementAt(i==0?rus.size()-1:i-1);
												Drivelane dlTo = (Drivelane) ln.elementAt(i);
												Roaduser rub = (Roaduser) rus.elementAt(i);
												dlTo.addRoaduserAtEnd(rub);
												rub.setCycleMoved(curCycle);
												Drivelane[] pblanes = dlFrom.getSign().getNode().getShortestPaths(ru_des, ru_type);
												// TO DO: updateRUMove, including ranges
								//tlc.updateRoaduserMove(ru, dlFrom, dlFrom.getSign(), 0, dlTo, dlTo.getSign(), rub.getPosition(), pbLanes, , destLane);
											}
											return;
										}
										else
										{
												ln.addElement(lane);
												moveLane(destLane, ln);
												if (lane.getCycleMoved()==curCycle) return;
										}
									}
								}
								// Let the RU touch the Sign to rerfesh/unload some statistical data
								// Ok now the lane that should have moved, moved so try again .........
								if(destLane.isLastPosFree(ru_len)) {
									// If the position is free, then move
									try{
										node.processStats(ru, curCycle, sign);
										destLane.addRoaduserAtEnd(ru);
										ru.setPrevSign(lane.getSign().getId());
										li.remove();
										tlc.updateRoaduserMove(ru, lane, sign, ru_pos, destLane, destLane.getSign(), ru.getPosition(), possiblelanes, ranges, destLane);
									}
									catch(Exception e) {}
								}
								else {
									// Apperently there was no space created on the lane
									moveRoaduserOnLane(li, ru, /*ru_speed,*/ lane); //1
									
									if(ru.getPosition()==ru_pos) {
										// Couldnt move
										tlc.updateRoaduserMove(ru, lane, sign, ru_pos, lane, sign, ru_pos, possiblelanes, ranges, destLane);
									}
									else {
										// Did move some
										tlc.updateRoaduserMove(ru, lane, sign, ru_pos, lane, sign, ru.getPosition(), possiblelanes, ranges, destLane);
									}
								}
							}
						}
					}
					else {
						if(moveRoaduserOnLane(li, ru, /*ru_speed,*/ lane)!=ru_speed) { //2
							// Light wasnt green, so just advanced some steps...
							// Did move some on this lane.
							tlc.updateRoaduserMove(ru, lane, sign, ru_pos, lane, sign, ru.getPosition(), possiblelanes, ranges, null);
						}
						// else Apparently I could overjump my neighbour orso :)
					}
				}
				else {
					/*	This is when the roaduser doesn't go around the corner
						The maximum amount of space per speed is travelled */
					moveRoaduserOnLane(li, ru, /*ru_speed,*/ lane);		//3	
					if(ru_pos == ru.getPosition()) { // Couldnt move
						tlc.updateRoaduserMove(ru, lane, sign, ru_pos, lane, sign, ru_pos, possiblelanes, ranges, null);
					}
					else {	// Did move some
						tlc.updateRoaduserMove(ru, lane, sign, ru_pos, lane, sign, ru.getPosition(), possiblelanes, ranges, null);
					}
				}
				if (ru!=null) ru.setCycleMoved(curCycle);								
			}
		}
		lane.setCycleAsked(curCycle);
		lane.setCycleMoved(curCycle);
	}
	
	protected int moveRoaduserOnLane(ListIterator li, Roaduser ru,/* int speed_left,*/ Drivelane lane) {
	
		// Previous should be 'ru'
		Roaduser prv = (Roaduser) li.previous();
		
		// move roaduser
		int ru_pos   = ru.getPosition();
		int ru_speed = ru.getSpeed();
		int ru_len   = ru.getLength();
		int ru_stopdistance = ru.getStopDistance();
		int best_pos = ru_pos;
		int max_pos = ru_pos;
		int target_pos = (ru_pos - ru_speed > 0) ? ru_pos - ru_speed : 0;
		//System.out.println("Targetpos:"+target_pos+" and hasPrev:"+li.hasPrevious());

			
		if(prv==ru && li.hasPrevious()) { // roaduser not first
			prv = (Roaduser) li.previous();
			int prv_pos = prv.getPosition();			
			max_pos = prv_pos + prv.getLength() + ru_stopdistance; // stopdistance support
			if(target_pos < max_pos && ru instanceof PacCar && !(prv instanceof PacCar)) 
			{
				// eat it, car-cannibalism!
				if (prv instanceof CustomRoaduser) CustomFactory.removeCustom((CustomRoaduser)prv);
				best_pos = target_pos < prv_pos ? prv_pos : target_pos;
				prv = null;
				li.remove();
			} 
			else 
			{
				if(max_pos < target_pos) {
					best_pos = target_pos;
				}
				else
					best_pos = max_pos;
				li.next();
			}
			//System.out.println("RU had previous, now bestpos ="+best_pos);
		}
		else { // roaduser first
			best_pos = target_pos;
		}
		li.next();
		
		if(best_pos != ru_pos) {
			// The Roaduser can advance some positions
			ru.setPosition(best_pos);
			return (ru_speed - (ru_pos - best_pos));
		}
		else
		{
			// check for aggressiveness
			if (dp instanceof AggressiveDP)	{
				AggressiveDP adp = (AggressiveDP) dp;
				Drivelane[] shortest = lane.getSign().getNode().getShortestPaths(ru.getDestNode().getId(), ru.getType());
				try{
					if (adp.checkNeighbourLanes(lane, ru, ru_speed, shortest)) {
						li.remove();
						ru.setColor(Color.darkGray);
					}
				}
				catch(InfraException e) { System.out.println("Something has become a little too aggressive."); }
			}
			return 0;
		}
	}

	/**
	 * All roadusers on this lane should be checked if they have to change speed. 
	 * @param lane the lane that has to be checked
	 */
	protected void checkRoaduserSpeed(Drivelane lane) {
		LinkedList queue;
		ListIterator li;
		Sign sign;

		sign = lane.getSign();
		queue = lane.getQueue();
		li = queue.listIterator();

		Roaduser ru;
		Roaduser prv = null;

		while (li.hasNext()) {
			try {
				ru = (Roaduser) li.next();
			} catch (Exception e) {
				// When this exception is thrown you removed the first element
				// of the queue, therefore re-create the iterator.
				System.out.println("CME");
				li = queue.listIterator();
				continue;
			}
			
			// (SBC) check distance to previous roaduser and adjust speed
			// Previous should be 'ru'
			//prv = (Roaduser) li.previous();

			//if (prv == ru && li.hasPrevious()) 
			if (prv != null)
			{ // roaduser is not the first
													// one
				//prv = (Roaduser) li.previous();
				int prv_pos = prv.getPosition();
				int prv_length = prv.getLength();
				int prv_speed = prv.getSpeed();
				//prv = (Roaduser) li.next();

				// check distance
				if (ru.getPosition() - (prv_pos + prv_length) > 2 * ru.getStopDistance())
					// if distance to the previous big enough, speed up
					ru.adjustSpeedTo(lane.getSpeedMaxAllowed());
				else {
					if (ru.getPosition() - (prv_pos + prv_length) < ru.getStopDistance())
						ru.adjustSpeedTo(0); // a little slower then the
												// previous
					else
						// if at good distance, follow the car in front
						ru.adjustSpeedTo(prv_speed);
				}

				// prv = (Roaduser) li.next();
			} 
			else 
			{ // roaduser is first roaduser on the lane
				// slow down if approaching red light
				if (lane.getSign().getType()==Sign.TRAFFICLIGHT && !lane.getSign().getState()) {
					if (ru.getPosition() < 4) // first roaduser just in front
												// of red light
						ru.adjustSpeedTo(0);
					else if (ru.getPosition() < 2 * ru.getStopDistance())
						ru.adjustSpeedTo(0);
					else ru.adjustSpeedTo(lane.getSpeedMaxAllowed());
				} else {
					// light is green
					// check if it can cross junction and if there is place left. 
					// if the roaduser cannot cross the juction because of no place on destination lane, it has to stop
					ru.adjustSpeedTo(lane.getSpeedMaxAllowed());
					try {
						int ru_type, ru_des, des_pos, des_length;
						Drivelane destLane;
						ru_type = ru.getType();
						ru_des = ru.getDestNode().getId();

						Drivelane[] shortestpaths = lane.getSign().getNode().getShortestPaths(ru_des, ru_type);

						Drivelane[] lanesleadingfrom = lane.getSign().getNode().getAvailableLanesLeadingFrom(lane, ru_type);
						destLane = dp.getDirection(ru, lane, lanesleadingfrom, shortestpaths);
						
						LinkedList destqueue = destLane.getQueue();
						Roaduser destru = (Roaduser)destqueue.getLast();
						des_length = destLane.getLength();
						des_pos = destru.getPosition();
						if ( ru.getPosition() < lane.getSpeedMaxAllowed() && des_pos > des_length + ru.getPosition() - ru.getSpeed() ) 
							ru.adjustSpeedTo(0);
						else ru.adjustSpeedTo(lane.getSpeedMaxAllowed());
					} catch (Exception e) {
					}					
				}
			}// ///////////////////////////////////////////////////////////////////////////
			prv = ru;

		}
	}
	
/*	public class SimModelFreqsUpdate extends Thread
	{
		
		public SimModelFreqsUpdate(){
		}
		
		
		public void run( ) {
			while (true) {
				try {
					sleep(1000);
					synchronized(this) {
						System.out.println("Se levanto el Thread!!");
						XMLLoader loader=new XMLLoader(new File("../RedesEjemplo/miRedPrueb3.infra"));
						loadAll(loader,getSimModel());
						newInfrastructure(model.getInfrastructure());
						loader.close();
						Enumeration	specialNodes = Arrayutils.getEnumeration(infra.getSpecialNodes());
						while (specialNodes.hasMoreElements())
							((SpecialNode)(specialNodes.nextElement())).;
						EdgeNode specialNodes[] = infra.getSpecialNodes();
						
					}
				} catch (Exception e) { }
			}		
		}
		
		
		
	}

*/


	/**
	 *
	 * The second thread that runs the simulation.
	 *
	 * @author Joep Moritz
	 * @version 1.0
	 */
	public class SimModelThread extends Thread
	{
		/** Is the thread suspended? */
		private volatile boolean suspended;
		/** Is the thread alive? If this is set to false, the thread will die gracefully */
		private volatile boolean alive;
		/** The time in milliseconds this thread sleeps after a call to doStep() */
		private int sleepTime = 100;
		
		/** Returns the current sleep time */
		public int getSleepTime() { return sleepTime; }
		/** Sets the sleep time */
		public void setSleepTime(int s) { sleepTime = s; }
		
		/**
		 * Starts the thread.
		 */
		public SimModelThread( ) {
			alive = true;
			suspended = true;
		}
		
		/**
		 * Suspends the thread.
		 */
		public synchronized void pause( ) {
			suspended = true;
		}
		
		/**
		 * Resumes the thread.
		 */
		public synchronized void unpause( ) {
			suspended = false;
			notify();
		}
		
		/**
		 * Stops the thread. Invoked when the program exitst.
		 * This method cannot be named stop().
		 */
		public synchronized void die( ) {
			alive = false;
			interrupt();
		}
		
		/**
		 * Returns true if the thread is not suspended and not dead
		 */
		public boolean isRunning( ) {
			return !suspended && alive;
		}
	
		/**
		 * Invokes Model.doStep() and sleeps for sleepTime milliseconds
		 */

		public void run( ) {
			while (alive) {
				try {
					sleep(sleepTime);
					synchronized(this) {
						while (suspended && alive)
							wait();
					}
					doStep();
				} catch (InterruptedException e) { }
			}		
		}
	}
	
	// Some XMLSerializable stuff 
	
	public void load (XMLElement myElement,XMLLoader loader) throws XMLTreeException,IOException,XMLInvalidInputException
	{	super.load(myElement,loader);
		setInfrastructure(infra);
		Dictionary loadDictionary;
		try
		{	loadDictionary=infra.getMainDictionary();
		}
		catch (InfraException ohNo)
		{	throw new XMLInvalidInputException
			("This is weird. The infra can't make a dictionary for the second "+
				"stage loader of the algorithms. Like : "+ohNo);
		}
		Dictionary infraDictionary=new Hashtable();
		infraDictionary.put("infra",infra);
		loadDictionary.put("infra",infraDictionary);
		boolean savedBySim=("simulator").equals(myElement.getAttribute("saved-by").getValue());
		if (savedBySim)
		{	thread.setSleepTime(myElement.getAttribute("speed").getIntValue());
			simName=myElement.getAttribute("sim-name").getValue();
			curCycle=myElement.getAttribute("current-cycle").getIntValue();
			TLCFactory factory=new TLCFactory(infra);
			tlc = null;
			
		    try
		    { tlc=factory.getInstanceForLoad
		        (factory.getNumberByXMLTagName
			 (loader.getNextElementName()));
		      loader.load(this,tlc);	 
		      System.out.println("Loaded TLC "+tlc.getXMLName());	 
		    }
		    catch (InfraException e2)
		    { throw new XMLInvalidInputException
		      ("Problem while TLC algorithm was processing infrastructure :"+e2);
		    }
		    tlc.loadSecondStage(loadDictionary);
		    DPFactory dpFactory=new DPFactory(this,tlc);
    		    try
		    { dp=dpFactory.getInstance
		        (dpFactory.getNumberByXMLTagName
			 (loader.getNextElementName()));
		      loader.load(this,dp);	 
		      System.out.println("Loaded DP "+dp.getXMLName());	 
		    }
		    catch (ClassNotFoundException e)
		    { throw new XMLInvalidInputException
		      ("Problem with creating DP in SimModel."+
		       "Could not generate instance of DP type :"+e);
		    }
    		dp.loadSecondStage(loadDictionary);
    		loader.load(this,sgnctrl);
    		sgnctrl.setTLC(tlc);
		 }
		 else {
			curCycle = 0;
		 }
		 while (loader.getNextElementName().equals("dispenser"))
		 	loader.load(this,new NumberDispenser());
	}
	
	public XMLElement saveSelf ()
	{ 	XMLElement result=super.saveSelf();
		result.addAttribute(new XMLAttribute("sim-name",simName));
		result.addAttribute(new XMLAttribute("saved-by","simulator"));
		result.addAttribute(new	XMLAttribute("speed",thread.getSleepTime()));
		result.addAttribute(new XMLAttribute("current-cycle",curCycle));
	  	return result;
	}
	
	public void saveChilds (XMLSaver saver) throws IOException,XMLTreeException,XMLCannotSaveException
	{	super.saveChilds(saver);	
		System.out.println("Saving TLC "+tlc.getXMLName());
		saver.saveObject(tlc);
		System.out.println("Saving DP "+dp.getXMLName());
		saver.saveObject(dp);
		saver.saveObject(sgnctrl);
	}
	
	protected void restartSpawnFreqs(){

		if (! hasRun)
		{	
			hasRun=true;
			initialize();
		}
		iFreqs = spawnFreqsList.iterator();
		nroFranja = 0;
	}
	
	
	protected void updateSpawnFreqs(){
		
		if (! hasRun)
		{	
			hasRun=true;
			initialize();
		}
		
		HashMap spawnFreqs = null;
		
		//while (true) {
			try {
		
				System.out.println("Se van a cambiar las frecuencias!!");
				if (!iFreqs.hasNext()){
					iFreqs = spawnFreqsList.iterator();
					nroFranja = 0;
				}
				nroFranja ++;
				System.out.println("Franja Horaria: " + nroFranja);
				spawnFreqs = (HashMap)iFreqs.next();
		
				
				Node[] nodos = infra.getAllNodes();
				int k = 0;
				for (int i=0;i<nodos.length;i++){
			
					if (nodos[i] instanceof EdgeNode){
						EdgeNode en = ((EdgeNode)nodos[i]);
						String[] freqs = (String[])spawnFreqs.get(Integer.toString(en.getId()));
						setSpawnFrequency(en,1,Float.parseFloat(freqs[0]));
						setSpawnFrequency(en,2,Float.parseFloat(freqs[1]));
						setSpawnFrequency(en,3,Float.parseFloat(freqs[2]));
					}
					//we could reinit the statistics, so they are not influenced by the old behaviour
					/*anyway, we will not reinit them here, they will be reinitiated after we export the
					 * statistics of the current simulation
					 */ 
			 		/*
					nodos[i].initStats();
					Drivelane[] lanes = nodos[i].getAllLanes();
					for (int j = 0; j < lanes.length; j++) {
						Drivelane drivelane = lanes[j];
						drivelane.initStats();
					}*/
				}
				
			} catch (Exception e) {
				System.out.println("Se lanz?una excepci�n");
				e.printStackTrace();
			}
			infra.reset();
		
		}
	
	
	
		/**
		* GASTON: Send the statistics to the DLC module.
		*/
		public void sendStatistics()
		{
			active = true;
			//new StatisticsController(this);
		}
	
	
	
		public class StatisticsSender extends Thread{
			
			SimModel model = null;
			
			
			boolean alive = true;

			
			public void run() {
				while (alive) {
					
					try {
						synchronized(this){
							while (!active)
								sleep(500);
						}
						
						new StatisticsController(model,rb);
						active = false;
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			

			public SimModel getModel() {
				return model;
			}

			/**
			 * @param b
			 */
			public void setAlive(boolean b) {
				alive = b;
			}

			/**
			 * @param model
			 */
			public void setModel(SimModel model) {
				this.model = model;
			}

		}
	
	
	/**
	 * @return
	 */
	public ResourceBundle getResourceBundle() {
		return rb;
	}

	/**
	 * @param bundle
	 */
	public void setResourceBundle(ResourceBundle bundle) {
		rb = bundle;
	}

}